package com.mpgame.database.redis.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Map;

import com.mpgame.database.constants.Constants;
import com.mpgame.database.redis.model.UserTokenInfo;
import com.mpgame.utils.Log;

public class TokenUtil {
    private TokenUtil() {
    }

    public static int TOKEN_TIMEOUT = 10;

    /** 产生token并存储关键信息 */
    public static String genToken(UserTokenInfo userTokenInfo) {
        try {
            // 先清理旧token避免一个用户产生多个token
            String existedToken = getUserToken(userTokenInfo.getAccountId());
            if(existedToken != null && !existedToken.isEmpty()){
                invalidateToken(existedToken);
            }
            //invalidateUserTokens(userTokenInfo.getAccountId());

            String salt = genSalt();

            long curTime = System.currentTimeMillis();
            String tokenData = userTokenInfo.getAccountId() + ":" + userTokenInfo.getUsername() + ":" + curTime + ":"
                    + salt;

            String signature = genSignature(tokenData);

            String token = Base64.getUrlEncoder()
                    .withoutPadding()
                    .encodeToString((tokenData + ":" + signature).getBytes());

            storeTokenToRedis(token, userTokenInfo);

            Log.info("Token生成成功：" + userTokenInfo.getAccountId());
            return token;
        } catch (Exception e) {
            Log.err("Token生成失败：" + e.getMessage());
            return null;
        }
    }

    public static boolean validateToken(String token) {
        if (token == null || token.isEmpty())
            return false;

        try {
            String tokenKey = getTokenPrefix(token);
            if (!RedisUtil.exists(tokenKey)) {
                Log.warn("Token在Redis中不存在或已过期：" + token);
                return false;
            }

            if (!validateTokenSignature(token)) {
                Log.warn("Token签名验证失败：" + token);
                return false;
            }

            return true;

        } catch (Exception e) {
            Log.err("Token验证失败：" + e.getMessage());
            return false;
        }
    }

    public static UserTokenInfo getUserTokenInfoFromToken(String token) {
        try {
            String tokenKey = getTokenPrefix(token);
            Map<String, String> tokenData = RedisUtil.hgetall(tokenKey);
            if (tokenData.isEmpty()) {
                return null;
            }

            String acctId = tokenData.get("accountId");

            String sessionKey = getSessionPrefix(acctId);
            Map<String, String> sessionData = RedisUtil.hgetall(sessionKey);

            UserTokenInfo userTokenInfo = new UserTokenInfo();
            userTokenInfo.setAccountId(Integer.parseInt(sessionData.get("accountId")));
            userTokenInfo.setUsername(sessionData.get("username"));
            userTokenInfo.setNickname(sessionData.get("nickname"));
            userTokenInfo.setLastLoginTimestamp(Long.parseLong(sessionData.getOrDefault("lastLoginTimestamp",
                    "0")));
            userTokenInfo.setLastActiveTimestamp(Long.parseLong(sessionData.getOrDefault("lastActiveTimestamp",
                    "0")));
            userTokenInfo.setToken(token);
            userTokenInfo.setPosX(Float.parseFloat(sessionData.get("posX")));
            userTokenInfo.setPosY(Float.parseFloat(sessionData.get("posY")));
            userTokenInfo.setSceneId(Integer.parseInt(sessionData.get("sceneId")));

            return userTokenInfo;
        } catch (Exception e) {
            Log.err("由Token获取用户信息失败：" + e.getMessage());
            return null;
        }
    }

    public static UserTokenInfo getUserTokenInfoFromAccountId(int accountId) {
        try {
            String sessionKey = getSessionPrefix(accountId);
            Map<String, String> sessionData = RedisUtil.hgetall(sessionKey);

            UserTokenInfo userTokenInfo = new UserTokenInfo();
            userTokenInfo.setAccountId(Integer.parseInt(sessionData.get("accountId")));
            userTokenInfo.setUsername(sessionData.get("username"));
            userTokenInfo.setNickname(sessionData.get("nickname"));
            userTokenInfo.setLastLoginTimestamp(Long.parseLong(sessionData.getOrDefault("lastLoginTimestamp", "0")));
            userTokenInfo.setLastActiveTimestamp(Long.parseLong(sessionData.getOrDefault("lastActiveTimestamp", "0")));
            userTokenInfo.setToken(sessionData.get("token"));
            userTokenInfo.setPosX(Float.parseFloat(sessionData.get("posX")));
            userTokenInfo.setPosY(Float.parseFloat(sessionData.get("posY")));
            userTokenInfo.setSceneId(Integer.parseInt(sessionData.get("sceneId")));

            return userTokenInfo;
        } catch (Exception e) {
            Log.err("由Account Id获取用户信息失败：" + e.getMessage());
            return null;
        }
    }

    /** 使token失效 */
    public static boolean invalidateToken(String token) {
        try {
            String tokenKey = getTokenPrefix(token);
            RedisUtil.del(tokenKey);
            return true;
        } catch (Exception e) {
            Log.err("Token失效失败：" + e.getMessage());
            return false;
        }
    }

    /** 令指定用户token及会话信息失效 */
    // public static boolean invalidateUserTokens(int accountId) {
    //     try {
    //         String existedToken = getUserToken(accountId);
    //         if (existedToken != null && !existedToken.isEmpty()) {
    //             invalidateToken(existedToken);
    //         }
    //         Log.info("用户先前Token失效：" + accountId);
    //         return true;
    //     } catch (Exception e) {
    //         Log.err("用户Token失效失败：" + e.getMessage());
    //         return false;
    //     }
    // }

    /** 获取用户当前Token */
    public static String getUserToken(int accountId) {
        try {
            String sessionKey = getSessionPrefix(accountId);
            Map<String, String> sessionData = RedisUtil.hgetall(sessionKey);

            if (sessionData.isEmpty()) {
                return null;
            }

            return sessionData.get("token");
        } catch (Exception e) {
            Log.err("获取用户Token列表失败：" + e.getMessage());
            return null;
        }
    }

    private static String genSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(salt);
    }

    private static String genSignature(String data) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            String dataWithSecret = data + ":" + Constants.CToken.SECRET_KEY;
            byte[] hash = digest.digest(dataWithSecret.getBytes());
            return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
        } catch (NoSuchAlgorithmException e) {
            Log.err("SHA-256算法不可用：" + e.getMessage());
            return null;
        }
    }

    private static boolean validateTokenSignature(String token) {
        try {
            byte[] decodedBytes = Base64.getUrlDecoder().decode(token);
            String decodedToken = new String(decodedBytes);

            String[] parts = decodedToken.split(":");
            if (parts.length != 5) {
                return false;
            }

            String data = parts[0] + ":" + parts[1] + ":" + parts[2] + ":" + parts[3];
            String signature = parts[4];

            String expectedSignature = genSignature(data);
            return signature.equals(expectedSignature);
        } catch (Exception e) {
            Log.err("Token签名验证异常：" + e.getMessage());
            return false;
        }
    }

    private static void storeTokenToRedis(String token, UserTokenInfo userTokenInfo) {
        try {
            String tokenKey = getTokenPrefix(token);
            String sessionKey = getSessionPrefix(userTokenInfo.getAccountId());

            RedisUtil.hset(tokenKey, "accountId", String.valueOf(userTokenInfo.getAccountId()));
            RedisUtil.expire(tokenKey, Constants.CToken.TOKEN_EXPIRE_TIME);

            boolean sessionExists = RedisUtil.exists(sessionKey);
            if (sessionExists) {
                RedisUtil.hset(sessionKey, "lastLoginTimestamp", String.valueOf(userTokenInfo.getLastLoginTimestamp()));
                RedisUtil.hset(sessionKey, "token", token);
                // 刷新会话过期时间
                RedisUtil.expire(sessionKey, Constants.CToken.SESSION_EXPIRE_TIME);
                Log.info("更新现有会话信息登录时间：" + userTokenInfo.getAccountId());
            } else {
                // 存储会话信息
                RedisUtil.hset(sessionKey, "accountId", String.valueOf(userTokenInfo.getAccountId()));
                RedisUtil.hset(sessionKey, "username", userTokenInfo.getUsername());
                RedisUtil.hset(sessionKey, "nickname", userTokenInfo.getNickname());
                RedisUtil.hset(sessionKey, "lastLoginTimestamp", String.valueOf(userTokenInfo.getLastLoginTimestamp()));
                RedisUtil.hset(sessionKey, "lastActiveTimestamp",
                        String.valueOf(userTokenInfo.getLastActiveTimestamp()));
                RedisUtil.hset(sessionKey, "token", token);
                RedisUtil.hset(sessionKey, "posX", String.valueOf(userTokenInfo.getPosX()));
                RedisUtil.hset(sessionKey, "posY", String.valueOf(userTokenInfo.getPosY()));
                RedisUtil.hset(sessionKey, "sceneId", String.valueOf(userTokenInfo.getSceneId()));
                RedisUtil.expire(sessionKey, Constants.CToken.SESSION_EXPIRE_TIME);
                Log.info("创建新会话信息：" + userTokenInfo.getAccountId());
            }

            // Gateway 认证成功后加入在线用户列表
        } catch (Exception e) {
            Log.err("存储Token到Redis失败：" + e.getMessage());
        }
    }

    public static String getTokenPrefix(String token) {
        return Constants.CToken.TOKEN_PREFIX + token;
    }

    public static String getSessionPrefix(int accountId) {
        return Constants.CToken.USER_SESSION_PREFIX + String.valueOf(accountId);
    }

    public static String getSessionPrefix(String accountId) {
        return Constants.CToken.USER_SESSION_PREFIX + accountId;
    }
}
